// Copyright 2012 Google Inc. All Rights Reserved.

package com.google.appengine.preferencessync.adapter;

import com.google.appengine.preferencessync.internal.Config;
import com.google.appengine.preferencessync.internal.InternalSyncException;
import com.google.appengine.preferencessync.internal.InternalSyncException.ExceptionType;
import com.google.appengine.preferencessync.internal.InternalSyncUtils;
import com.google.appengine.preferencessync.internal.SyncClientLog;

import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;

public class SyncableSharedPreferencesAdapter extends AbstractThreadedSyncAdapter {

  /**
   * This error message gets shown when user clicks on sync with a different username in
   * android settings.
   */
  private static final String WRONG_ACCOUNT_ERROR_STRING =
      "Sync called for a wrong account. Expected account %s but syncing for account %s";

  public final AbstractSyncablePrefsService.RequestProcessor requestProcessor;

  public SyncableSharedPreferencesAdapter(
      AbstractSyncablePrefsService.RequestProcessor requestProcessor, Context context,
      boolean autoInitialize) {
    super(context, autoInitialize);
    this.requestProcessor = requestProcessor;
  }

  /**
   * Called when sync adapter schedules a sync. Syncs all SharedPreferences.
   */
  @Override
  public void onPerformSync(Account account, Bundle extras, String authority,
      ContentProviderClient provider, SyncResult syncResult) {
    Config config = Config.getDefaultConfig(getContext());

    synchronized(SyncableSharedPreferencesAdapter.class) {
      // Is the configuration sane?
      if (!config.isSane()) {
        return;
      }

      // Check whether sync is called on the right account.
      if (!account.name.equals(config.getUsername())) {
        SyncClientLog.logDebug(this.getClass(),
            String.format(WRONG_ACCOUNT_ERROR_STRING, config.getUsername(), account.name));
        // Switch off sync automatically for the account to avoid errors.
        ContentResolver.setSyncAutomatically(account, authority, false);
        return;
      }

      try {
        SyncClientLog.logDebug(this.getClass(), "Calling doSync");
        InternalSyncUtils.doSync(config, getContext(), requestProcessor);
      } catch (InternalSyncException e) {
        SyncClientLog.log(getClass(), e);
        setErrors(e, syncResult);
      } catch (Throwable e) {
        SyncClientLog.logError(getClass(), e);
      }
    }
  }

  /**
   * Sets errors in syncresult so that the sync adapter can schedule the next sync.
   *
   * @param e
   * @param result
   */
  private void setErrors(InternalSyncException e, SyncResult result) {
    if (e.getType() == ExceptionType.INTERNAL_HTTP_TIMEOUT
        || e.getType() == ExceptionType.INTERNAL_HTTP_STATUS_CONFLICT) {
      // A soft error. Hence does exponential backoff.
      result.stats.numIoExceptions++;
    }
  }
}
